#include <interface.h>
#include <iostream>
#include <vector>

DECLARE_DRAWABLE_BEGIN(CameraClass, "Getting started/Camera", 1,9,2)
  virtual void init() override {
    // build and compile our shader program
    // ------------------------------------
    shader_.loadPath("texture.vs", "texture.fs");

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
      -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
      0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
      0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
      0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
      -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
      -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

      -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
      0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
      0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
      0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
      -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
      -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

      -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
      -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
      -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
      -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
      -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
      -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

      0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
      0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
      0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
      0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
      0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
      0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

      -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
      0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
      0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
      0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
      -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
      -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

      -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
      0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
      0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
      0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
      -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
      -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };

    cubes_.clear();
    cubes_.emplace_back(glm::vec3( 0.0f,  0.0f,  0.0f));
    cubes_.emplace_back(glm::vec3( 2.0f,  5.0f, -15.0f));
    cubes_.emplace_back(glm::vec3(-1.5f, -2.2f, -2.5f));
    cubes_.emplace_back(glm::vec3(-3.8f, -2.0f, -12.3f));
    cubes_.emplace_back(glm::vec3 (2.4f, -0.4f, -3.5f));
    cubes_.emplace_back(glm::vec3(-1.7f,  3.0f, -7.5f));
    cubes_.emplace_back(glm::vec3( 1.3f, -2.0f, -2.5f));
    cubes_.emplace_back(glm::vec3( 1.5f,  2.0f, -2.5f));
    cubes_.emplace_back(glm::vec3( 1.5f,  0.2f, -1.5f));
    cubes_.emplace_back(glm::vec3(-1.3f,  1.0f, -1.5f));

    glGenVertexArrays(1, &VAO_);
    glGenBuffers(1, &VBO_);

    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO_);

    glBindBuffer(GL_ARRAY_BUFFER, VBO_);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // texture coord attribute
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    texture1_.loadPath("container.jpg");
    texture2_.loadPath("awesomeface.png");

    shader_.use();
    glUniform1i(glGetUniformLocation(shader_.id, "texture1"), 0);
    shader_.setInt("texture2", 1);

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    glBindVertexArray(0);

    /////////////////////////////
    model_ = glm::mat4();
    view_ = glm::mat4();
    projection_ = glm::mat4();
    rotateSpeed_ = 30.f;
    camera_.position = glm::vec3(0.f, 0.f, 3.f);

    model_ = glm::rotate(model_, glm::radians(-55.f), glm::vec3(1.f, 0.f, 0.f));
  }
  virtual void uninit() override {
    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &VAO_);
    glDeleteBuffers(1, &VBO_);
    texture1_.free();
    texture2_.free();
    shader_.free();
  }
  virtual void update(float elapsedTime) override {
    glGetIntegerv(GL_VIEWPORT, viewport_.value);

    float radians = glm::min(elapsedTime * rotateSpeed_, 360.f);
    radians = glm::max(radians, 0.f);
    model_ = glm::rotate(model_, glm::radians(radians), glm::vec3(0.5f, 0.1f, 0.f));

    projection_ = glm::perspective(glm::radians(camera_.zoom), viewport_.aspect() , 0.1f, 100.f);
    shader_.setMat4("projection", projection_);

    camera_.update(elapsedTime);
    view_ = camera_.viewMatrix();
    shader_.setMat4("view", view_);
  }
  virtual void render() override {
    // render the triangle
    // bind textures on corresponding texture units
    texture1_.use(GL_TEXTURE0);
    texture2_.use(GL_TEXTURE1);

    shader_.use();
    
    glBindVertexArray(VAO_);

    for (auto it = cubes_.begin(); it != cubes_.end(); ++it) {
      glm::mat4 model;
      if (it == cubes_.begin())
        model = model_;
      model = glm::translate(model, *it);
      shader_.setMat4("model", model);
      glDrawArrays(GL_TRIANGLES, 0, 36);
    }
  }
  virtual void processInput(sdl::event::Event &evt) override {
    if (evt.type == SDL_KEYDOWN) {
      switch (evt.key.keysym.sym) {
        case SDLK_w: camera_.processKeyboard(CameraMovement::Forward); break;
        case SDLK_s: camera_.processKeyboard(CameraMovement::Backward); break;
        case SDLK_a: camera_.processKeyboard(CameraMovement::Left); break;
        case SDLK_d: camera_.processKeyboard(CameraMovement::Right); break;
      }
    } else if (evt.type == SDL_MOUSEMOTION) {
      if (evt.motion.state & SDL_BUTTON_LMASK)
        camera_.processMouseMovement(evt.motion.xrel, -evt.motion.yrel);
    } else if (evt.type == SDL_MOUSEWHEEL) {
      camera_.processMouseScroll(evt.wheel.y);
    }
  }
 protected:
  GLuint VAO_, VBO_;
  Shader shader_;
  Texture texture1_, texture2_;
  float rotateSpeed_;
  glm::mat4 model_, view_, projection_;
  union {
    struct {
      GLint x, y, w, h;
    };
    GLint value[4];
    float aspect() { return (w / h); }
  } viewport_;
  std::vector<glm::vec3> cubes_;
  Camera camera_;
DECLARE_DRAWABLE_END(CameraClass)